package dhz.storage.util;

import org.apache.shiro.SecurityUtils;
import org.apache.tools.zip.ZipFile;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.Set;

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;

@Service
public class FileUtil {
    /**
     * 删除一个文件，使用java.nio完成
     * @param target 被删除文件的路径
     * @throws NoSuchFileException 文件不存在
     * @throws IOException IO过程中出现问题
     * */
    private static void deleteNormalFile(Path target)throws IOException{
        if(!Files.exists(target)){
            throw new IOException();
        }else{
            Files.delete(target);
        }
    }

    /**
     * 在一个位置创建一个新的文件夹
     * @param target 文件夹的路径
     * @throws IOException IO过程中出现问题
     * @throws UserPrincipalNotFoundException 没有这个用户
     * @throws FileAlreadyExistsException 这个文件夹已经存在
     * */
    public static void mkdir(Path target)throws IOException{
        if(Files.exists(target))throw new IOException();
        Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("rwx------");// 用于设置文件夹的700属性
        FileAttribute<Set<PosixFilePermission>> attribute = PosixFilePermissions.asFileAttribute(permissions);
        Files.createDirectory(target, attribute);// 将上面的属性设置给文件夹
        String identification = SecurityUtils.getSubject().getPrincipal().toString();// 当前用户
        UserPrincipal this_user = FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName(identification);
        Files.setOwner(target, this_user);// 将新创建的文件夹设置成目标用户
    }

    /**
     * 删除一个文件夹，使用nio递归的删除掉文件夹下面的全部文件
     * @param target 企图删掉的文件夹
     * */
    private static void deleteFolder(Path target) throws IOException {
        Files.walkFileTree(target, new SimpleFileVisitor<Path>() {
            /**
             * 访问普通文件的时候，将文件删除
             * */
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Files.delete(file);
                return FileVisitResult.CONTINUE;
            }

            /**
             * 离开一个路径的时候，将空文件夹删除
             * */
            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                if(exc == null){// 如果底下没有发生任何错误，那就表明下面的内容被删完了，可以删除掉这个空目录了
                    Files.delete(dir);
                    return FileVisitResult.CONTINUE;
                }else{// 下面出了问题，显然删不了这个空目录，那就往上抛
                    throw exc;
                }
            }
        });
    }

    /**
     * 删除一个文件/文件夹
     * @param target 被删除的文件的路径
     * @throws IOException 删除过程中出现IO错误
     * @throws NoSuchFileException 这个文件不存在
     * */
    public static void delete(Path target)throws IOException{
        if(!Files.exists(target)){
            throw new NoSuchFileException(target.toString());
        }else if(Files.isDirectory(target)){// 删除文件夹
            FileUtil.deleteFolder(target);
        }else{// 删除普通文件
            FileUtil.deleteNormalFile(target);
        }
    }

    /**
     * 将一个普通文件从一个地方复制到另一个地方，不会覆盖文件
     * @param src 源文件，已经创建完毕
     * @param dst 目标文件的路径
     * @throws NoSuchFileException 源文件或者目标文件的父路径不存在
     * @throws FileAlreadyExistsException 目标文件已经存在了
     * */
    private static void copyNormalFile(Path src, Path dst) throws IOException{
        if(!Files.exists(dst.getParent())){// 父路径不存在
            throw new NoSuchFileException(dst.getParent().toString());
        }else if(Files.exists(dst)){// 已经存在这个文件了
            throw new FileAlreadyExistsException(dst.toString());
        }
        if(!Files.exists(src)){// 源文件不存在
            throw new NoSuchFileException(src.toString());
        }
        Files.copy(src, dst, StandardCopyOption.COPY_ATTRIBUTES);// 保证二者的属性一样
        Files.setPosixFilePermissions(dst, Files.getPosixFilePermissions(src));// 保证二者的权限一样
        Files.setOwner(dst, Files.getOwner(src));// 保证二者的所有者一样
    }

    /**
     * 复制一整个文件夹里面的东西到另一个地址
     * @param src 源文件夹
     * @param dst 目标文件夹
     * @throws NoSuchFileException 源文件不存在
     * */
    private static void copyDirectory(Path src, Path dst) throws IOException{
        if(!Files.exists(src))throw new NoSuchFileException(src.toString());
        Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
            /**
             * 进入文件夹之前，先在对应位置创建空文件夹
             * */
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                Path new_folder = dst.resolve(src.relativize(dir));// 新的文件夹的位置
                FileAttribute<Set<PosixFilePermission>> perms = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"));
                Files.createDirectory(new_folder, perms);// 设置文件的权限
                String identification = SecurityUtils.getSubject().getPrincipal().toString();
                Files.setOwner(new_folder, FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName(identification));//设置文件的拥有者
                return FileVisitResult.CONTINUE;
            }

            /**
             * 访问文件，直接将文件拷贝过去即可
             * */
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Path new_file = dst.resolve(src.relativize(file));// 获得新文件的位置
                if(Files.isHidden(file))return FileVisitResult.CONTINUE;
                if(Files.exists(new_file))return FileVisitResult.CONTINUE;
                Files.copy(file, new_file, StandardCopyOption.COPY_ATTRIBUTES);
                Files.setOwner(new_file, Files.getOwner(file));// 设置二者的拥有者
                Files.setPosixFilePermissions(new_file, Files.getPosixFilePermissions(file));// 设置二者的权限
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * 将一个文件从一个地方拷贝到另一个地方
     * @param src 源文件
     * @param dst 目标文件
     * @throws IOException IO过程出现问题
     * */
    public static void copy(Path src, Path dst)throws IOException{
        if(Files.isDirectory(src)){
            copyDirectory(src, dst);
        }else{
            copyNormalFile(src, dst);
        }
    }

    /**
     * 将一个普通文件从一个地方挪动到另一个地方，本质上来讲是先复制过去然后再删除之
     * @param src 源文件
     * @param dst 目标文件，参考linux中的mv指令，需要已经计算出移动过去之后叫啥了
     * @throws NoSuchFileException 被移动的文件不存在
     * */
    public static void mv(Path src, Path dst)throws IOException{
        if(!Files.exists(src))throw new NoSuchFileException(src.toString());
        if(src.toString().compareTo(dst.toString()) == 0)return;
        FileUtil.copy(src, dst);
        FileUtil.delete(src);
    }

    /**
     * 重命名一个文件/文件夹，本质上是原地移动，需要保证二者的名字不同
     * @param target 被重新命名的文件或者文件夹
     * @param name 新的名字
     * @throws FileAlreadyExistsException 新名字已经被人占用了
     * @throws NoSuchFileException 被重命名的文件不存在
     * */
    public static void rename(Path target, String name)throws IOException{
        File new_file = new File((target.getParent().resolve(name)).toAbsolutePath().toString());
        if(!Files.exists(target))throw new NoSuchFileException(target.toAbsolutePath().toString());
        if(new_file.exists())throw new FileAlreadyExistsException(new_file.getPath());
        target.toFile().renameTo(new_file);
    }

    /**
     * 给出一个文件的原始路径，如果这个名字在同一个文件夹中已经被人用过了，那就在后面加上(1)
     * @param original_file_path 原始的路径的名字，最后一个/后面的是文件名
     * @return 合适的文件名，一般后面已经有(n)
     * */
    public static String getFileName(String original_file_path){
        int len = original_file_path.length();
        StringBuilder builder = new StringBuilder();
        int i = len - 1;
        while(original_file_path.charAt(i) != '/' && original_file_path.charAt(i) != '.'){
            builder.insert(0, original_file_path.charAt(i));
            i--;
        }
        String file_name;
        String ext;
        if(original_file_path.charAt(i) == '.'){// 有扩展名
            file_name = original_file_path.substring(0, i);
            ext = '.'+builder.toString();
        }else{
            ext = "";
            file_name = original_file_path;
        }
        File f = new File(file_name + ext);
        if(!f.exists())return file_name+ext;
        i = 1;
        while(true){
            f = new File(file_name + "("+i+")"+ext);
            if(!f.exists()){
                break;
            }
            i++;
        }
        return file_name +"("+i+")"+ext;
    }

    /**
     * 将一个文件夹打包成zip文件
     * @param src 被打包的文件夹的路径
     * @return 打包好的压缩包的名字
     */
    public static String packFolder(Path src) throws IOException{
        String result_name = src.toFile().getName()+String.valueOf(Calendar.getInstance().getTimeInMillis())+".zip";
        Path temp_path = Paths.get("/home/buaa/tmp/"+result_name);
        Files.createFile(temp_path);
        File temp = temp_path.toFile();
        OutputStream ous = new FileOutputStream(temp);
        ZipOutputStream zip_ous = new ZipOutputStream(ous);
        zip_ous.setEncoding("utf-8");
        Files.walkFileTree(src, new SimpleFileVisitor<>(){
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                ZipEntry this_dir = new ZipEntry(src.relativize(dir) + "/");// 新的路径
                zip_ous.putNextEntry(this_dir);
                zip_ous.closeEntry();
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                ZipEntry this_file = new ZipEntry(src.relativize(file).toString());// 新的文件
                zip_ous.putNextEntry(this_file);
                FileInputStream fis = new FileInputStream(file.toFile());
                BufferedInputStream bis = new BufferedInputStream(fis);
                byte[] read = new byte[1024];
                while(bis.read(read) != -1){
                    zip_ous.write(read);
                }
                bis.close();
                fis.close();
                zip_ous.closeEntry();
                return FileVisitResult.CONTINUE;
            }
        });
        zip_ous.close();
        ous.close();
        return temp.getPath();
    }


    /**
     * 解压缩一个zip文件，将里面的东西全部解压缩到dst里面，需要当前有用户登录
     * @param src 源文件，必须是zip
     * @param dst 目标路径，必须存在
     * @throws java.util.zip.ZipException src不是一个zip文件
     * @throws NotDirectoryException dst不是一个路径或者不存在
     * @throws IOException IO过程中出现为问题
     * */
    public static void unzip(Path src, Path dst)throws IOException{
        ZipFile zip = new ZipFile(src.toFile());
        Enumeration<ZipEntry> entries = zip.getEntries();
        String identification = SecurityUtils.getSubject().getPrincipal().toString();// 当前登录的用户
        UserPrincipal current_user = FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName(identification);
        while(entries.hasMoreElements()){
            ZipEntry entry = entries.nextElement();
            if(entry.isDirectory()){
                Path this_dir = dst.resolve(entry.getName());
                Files.createDirectory(this_dir);
                Files.setPosixFilePermissions(this_dir, PosixFilePermissions.fromString("rwx------"));// 设置文件的权限
                Files.setOwner(this_dir, current_user);
            }else{
                Path this_file = dst.resolve(entry.getName());
                Files.createFile(this_file);
                Files.setPosixFilePermissions(this_file, PosixFilePermissions.fromString("rwx------"));
                Files.setOwner(this_file, current_user);
                byte[] from = new byte[1024];
                BufferedInputStream bis = new BufferedInputStream(zip.getInputStream(entry));
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(this_file.toFile()));
                while(bis.read(from) != -1){
                    bos.write(from);
                }
                bos.close();
                bis.close();
            }
        }
    }
}
